<h1 id="-">第二章</h1>
<hr>
<h2 id="-">类的层次结构、属性与变量</h2>
<p>在上一节结束时我们创建两个类：Thing 和 Treasure，尽管事实上这两个类共享了一些功能（特别是两者都包含 &#39;name&#39;），但它们是没有联系的。</p>
<p>现在，这两个类的重复看起来是不值一提的。但是，当你开始写一些复杂的程序时，你的类将会包含大量的变量和方法；你真的想将同样的事情一遍又一遍的重复吗。</p>
<p>对于其中一个类是其它（祖先）类的特殊类型结构来说创建一个类层次是更有意义的，这种情况下它会自动继承（inherit）祖先类的特征。例如，在我们简单的冒险游戏中，Treasure 是 Thing 的一个特殊的类型，因此 Treasure 就会继承 Thing 类的特征。</p>
<div class="note">
    <b>类层次——祖先（Ancestors）和后代（Descendants）：</b>在这本书中，我会经常提及“后代”类继承（inherit）自它们祖先类，这些术语意味着“相关”类之间的一种类似于家庭的关系。Ruby 中每一个类只有一个父亲，然而，它可能在一个很长很大的家庭树中，有许多代的父母、祖父母等等...
</div>

<p>Thing 类的特征通常被定义在它内部，Treasure 类则会自动地“继承” Thing 类所有的的特性。所以，我们不需要再次对这些特征进行编码，而是额外添加一些 Treasures 类特有的特性。</p>
<p>通常地规则是，在创建类层次结构时，具有更多通用特征的类要比具有更多特殊特征的类层次更高一些。所以，只有一个 <em>name</em> 和 <em>description</em> 的 Thing 类是具有 <em>name</em>、<em>description</em> 以及 <em>value</em> 的 Treasure 类的祖先；Thing 类也可能是一些其它的特殊类的祖先，例如具有 <em>name</em>、<em>description</em> 以及 <em>exits</em> 的 Room 类。</p>
<div class="note">
    <dl>
        <dt><strong>一个父亲，有多个孩子...</strong></dt>
        <dd>
            <div class="text-center py-1">
                <img src="./images/chapter2_Class.png" />
            </div>
            Thing 类具有 <i>name</i> 和 <i>description</i> （在 Ruby 程序中，它们可能是内部变量 <code>@name</code> 和 <code>@description</code>）。Treasure 和 Room 类都派生自 Thing 类，所以它们自动地继承了 <i>name</i> 和 <i>description</i> 。Treasure 类添加了一个 <i>value</i>，所以它就具有 <i>name</i>、<i>description</i> 和 <i>value</i> ；Room 类添加了一个 <i>exits</i>，所以它就具有 <i>name</i>、<i>description</i> 和 <i>exits</i> 。
        </dd>
    </dl>
</div>

<div class="code-file clearfix"><span>1adventure.rb</span></div>

<p>来让我们看看如何在 Ruby 中创建一个后代类。加载 <strong>1adventure.rb</strong> 程序，这只需要定义一个具有两个实例变量（instance variables）<code>@name</code> 和 <code>@description</code> 的 Thing 类，它们的值可以在创建新的 Thing 对象时在 <code>initialize</code> 方法中赋值。</p>
<p>实例变量通常不能（也不应该）被外部直接访问，这是为了遵循上一章讲到的封装（ encapsulation）原则。为了获得我们所需要的每个变量的值，我们需要一个 get 访问器方法，例如 <code>get_name</code>；为了给某个变量赋一个新值，我们也需要一个 set 访问器方法，例如 <code>set_name</code>。</p>
<h3 id="-">超类与子类</h3>
<p>现在来看看 Treasure 类，注意声明方式：</p>
<pre><code><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Treasure</span> &lt; Thing</span></code></pre><p>尖括号 <code>&lt;</code> 表示 Treasure 是 Thing 的一个子类（后代类），因此它从 Thing 类继承数据（变量）和行为（方法）。因为 <code>get_name</code>、<code>set_name</code>、<code>get_description</code> 以及 <code>set_description</code> 方法已经存在于 Thing 类中，所以不需要在后代类（Treasure）中再编写了。</p>
<p>Treasure 类还有额外的数据，也就是它的 value（<code>@value</code>），我已经为它编写了 <em>get</em> 和 <em>set</em> 访问器了。当创建一个新的 Treasure 对象时，它的 <code>initialize</code> 方法会被自动调用。一个 Treasure 对象有三个变量（<code>@name</code>、<code>@description</code> 和 <code>@value</code>）需要初始化，所以它的 <code>initialize</code> 方法有三个参数。前两个参数使用 <code>super</code> 关键字传递给超类（Thing）的 <code>initialize</code> 方法，以便 Thing 类的 <code>initialize</code> 方法可以处理它们：</p>
<pre><code><span class="hljs-function"><span class="hljs-title">super</span><span class="hljs-params">(aName, aDescription)</span></span></code></pre><p>当在方法中使用时，<code>super</code> 关键字调用在祖先类或超类中的与当前所在方法的同名方法。如果 <code>super</code> 关键字没有指定任何参数，当前方法的所有参数将会传递给祖先类的方法。但是，像本例中一样，提供了一个特定的参数列表时（这里是 <code>aName</code> 和 <code>aDescription</code>），那么只有这些参数会被传递给祖先类的方法。</p>
<h3 id="-">向超类传递参数</h3>
<p>当调用超类的方法时，括号是很重要的！如果参数列表为空，并且没有使用括号，那么所有的参数都将传递给超类。但是，参数列表为空，并且使用了括号时，将不会给超类传递任何参数。</p>
<div class="code-file clearfix"><span>super_args.rb</span></div>

<pre><code><span class="hljs-comment"># This passes a, b, c to the superclass</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">initialize</span><span class="hljs-params">(a, b, c, d, e, f)</span></span>
  <span class="hljs-keyword">super</span>(a, b, c)
<span class="hljs-keyword">end</span>

<span class="hljs-comment"># This passes a, b, c to the superclass</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">initialize</span><span class="hljs-params">(a, b, c)</span></span>
  <span class="hljs-keyword">super</span>
<span class="hljs-keyword">end</span>

<span class="hljs-comment"># This passes no arguments to the superclass</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">initialize</span><span class="hljs-params">(a, b, c)</span></span>
  <span class="hljs-keyword">super</span>()
<span class="hljs-keyword">end</span></code></pre><div class="note">
    要更好的了解 <code>super</code> 关键字的使用，请参阅本章末尾的<b>深入探索</b>部分。
</div>

<h3 id="-">访问器方法</h3>
<p>虽然这些类在冒险游戏中运行的足够好，但它们仍然是累赘的，因为设置了大量的 <em>get</em> 和 <em>set</em> 访问器方法。让我们看看有什么补救措施。</p>
<p>替换掉 <code>@description</code> 实例变量的两个不同的方法，<code>get_description</code> 和 <code>set_description</code>。</p>
<pre><code><span class="hljs-function"><span class="hljs-title">puts</span><span class="hljs-params">(t1.get_description)</span></span>
t1.set_description(<span class="hljs-string">"Some description"</span>)</code></pre><p>取值和赋值也许更好一些，对于一个简单的变量使用下面的方式进行取值和赋值：</p>
<pre><code><span class="hljs-function"><span class="hljs-title">puts</span><span class="hljs-params">(t1.description)</span></span>
t1<span class="hljs-selector-class">.description</span> = <span class="hljs-string">"Some description"</span></code></pre><p>为了能够做到这一点，我们需要修改 Treasure 类的定义。实现这一点的方法是重写 <code>@description</code> 的访问器方法：</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">description</span></span>
  <span class="hljs-keyword">return</span> @description
<span class="hljs-keyword">end</span>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">description=</span><span class="hljs-params">(aDescription)</span></span>
  @description = aDescription
<span class="hljs-keyword">end</span></code></pre><div class="code-file clearfix"><span>accessors1.rb</span></div>

<p>我已经在 <strong>accessors1.rb</strong> 中添加了类似于上面的访问器。<em>get</em> 访问器被称为 <code>description</code>，<em>set</em> 访问器被称为 <code>description=</code>（即就是，将等号（=）附加到 <em>get</em> 访问器方法名后面）。现在，就可以将一个新的字符串进行赋值了：</p>
<pre><code>t.description = <span class="hljs-comment">"a bit faded and worn around the edges"</span></code></pre><p>你可以像这样取值：</p>
<pre><code><span class="hljs-function"><span class="hljs-title">puts</span><span class="hljs-params">(t.description)</span></span></code></pre><h3 id="-set-">&#39;Set&#39; 访问器</h3>
<p>当你想要以这种方式编写 set 访问器时，必须将 <code>=</code> 字符附加到方法名后面，而不仅仅是将其放置于方法名和参数之间。</p>
<p>所以，这样是正确的：</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">name=</span><span class="hljs-params">(aName)</span></span></code></pre><p>但这样是错误的：</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">name</span> = <span class="hljs-params">(aName)</span></span></code></pre><h3 id="-">属性的读与写</h3>
<p>事实上，有一个更简单的方式实现相同的功能。所有你要做的就是使用两个特殊的方法，<code>attr_reader</code> 和 <code>attr_writer</code>，后跟一个符号（symbol）：</p>
<pre><code><span class="hljs-keyword">attr_reader</span> <span class="hljs-symbol">:description</span>
<span class="hljs-keyword">attr_writer</span> <span class="hljs-symbol">:description</span></code></pre><p>你可以添加这些代码到你的类定义中：</p>
<pre><code><span class="hljs-keyword">class</span> <span class="hljs-symbol">Thing</span>
  <span class="hljs-symbol">attr_reader</span> :<span class="hljs-symbol">description</span>
  <span class="hljs-symbol">attr_writer</span> :<span class="hljs-symbol">description</span>
    # <span class="hljs-symbol">maybe</span> <span class="hljs-symbol">some</span> <span class="hljs-symbol">more</span> <span class="hljs-symbol">methods</span> <span class="hljs-symbol">here</span>…
<span class="hljs-symbol">end</span></code></pre><p>使用一个符号并调用 <code>attr_reader</code> 方法将会为命名与该符号（<code>:description</code>）相匹配的实例变量（<code>@description</code>）创建一个 <em>get</em> 访问器。</p>
<p>调用 <code>attr_writer</code> 方法类似的将会为实例变量创建一个 <em>set</em> 访问器。实例变量被认为是一个对象的“属性”（attributes），这就是为什么 <code>attr_reader</code> 和 <code>attr_writer</code> 方法是这样命名的。</p>
<div class="note">
    <dl>
        <dt>符号（Symbols）</dt>
        <dd>
            在 Ruby 中，一个符号是以冒号开头的（例如，<code>:description</code>）。<strong>Symbol</strong> 类在 Ruby 类库中定义，用来表示解释器中的命名。当你将一个或多个符号作为参数传递给 <code>attr_reader</code>（这是一个 <strong>Module</strong> 类的方法）时，Ruby 会创建一个实例变量和一个 <em>get</em> 访问器方法。此访问器方法返回相应变量的值；实例变量和访问器方法都会使用该符号命名。所以，<code>attr_reader(:description)</code> 会创建一个名为 <code>@description</code> 的实例变量，以及一个名为 <code>description()</code> 的访问器方法。
        </dd>
    </dl>
</div>

<div class="code-file clearfix"><span>accessors2.rb</span></div>

<p><strong>accessors2.rb</strong> 程序包含一些属性读取器的示例。Thing 类为 <code>@name</code> 属性明确定义了 <em>get</em> 访问器方法，编写这样一个完整的方法的好处是，可以让你进行额外的处理，而不是简单的对属性的值进行读写。这里的 <em>get</em> 访问器使用 <code>String.capitalize</code> 方法返回 <code>@name</code> 的字符串值及其初始值的大写形式。</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">name</span></span>
  <span class="hljs-keyword">return</span> <span class="hljs-variable">@name</span>.capitalize
<span class="hljs-keyword">end</span></code></pre><p>当为 <code>@name</code> 属性进行赋值时，我不需要进行特殊的处理，所以我可以这么写：</p>
<pre><code><span class="hljs-keyword">attr_writer</span> <span class="hljs-symbol">:name</span></code></pre><p><code>@description</code> 属性不需要特殊处理，所以我使用 <code>attr_reader</code> 和 <code>attr_writer</code> 去获取和设置 <code>@description</code> 变量的值：</p>
<pre><code><span class="hljs-keyword">attr_reader</span> <span class="hljs-symbol">:description</span>
<span class="hljs-keyword">attr_writer</span> <span class="hljs-symbol">:description</span></code></pre><div class="note">
    <dl>
        <dt>Attributes or Properties?</dt>
        <dd>
            不要对术语感到困惑，在 Ruby 中，"Attribute" 相当于许多编程语言中的 "Propertie"。
        </dd>
    </dl>
</div>

<p>当你想允许对一个变量同时可以进行读和写操作时，<code>attr_accessor</code> 方法提供了替代 <code>attr_reader</code> 和 <code>attr_writer</code> 方法的简洁语法。我已经使用它来访问 Treasure 类中的 value 属性：</p>
<pre><code><span class="hljs-keyword">attr_accessor</span> <span class="hljs-symbol">:value</span></code></pre><p>这等同于：</p>
<pre><code><span class="hljs-keyword">attr_reader</span> <span class="hljs-symbol">:value</span>
<span class="hljs-keyword">attr_writer</span> <span class="hljs-symbol">:value</span></code></pre><p>前面我说过，使用一个符号并调用 <code>attr_reader</code> 实际上会创建一个名字与符号相同的变量。<code>attr_accessor</code> 方法也是一样的。</p>
<p>在 Thing 类的代码中，因为 <code>initialize</code> 方法显式创建了变量，所以这并不明显。然而，Treasure 类没有在它的 <code>initialize</code> 方法中引用 <code>@value</code> 变量。只存在 <code>@value</code> 访问器的定义：</p>
<pre><code><span class="hljs-keyword">attr_accessor</span> <span class="hljs-symbol">:value</span></code></pre><p>在我的源文件代码的底部，每个 Treasure 对象创建后都单独设置了 value 的值。</p>
<pre><code>t1<span class="hljs-selector-class">.value</span> = <span class="hljs-number">800</span></code></pre><p>即使从没有正式的声明，<code>@value</code> 变量却是真实存在的，我们能够使用 <em>get</em> 访问器获取其数值。</p>
<pre><code>t<span class="hljs-number">1</span>.<span class="hljs-keyword">value</span></code></pre><p>要绝对地确定属性访问器真的已经创建了 <code>@value</code>，你可以使用 <code>inspect</code> 方法查看对象的内部。我在这个程序的最后两行代码就是这么做的：</p>
<pre><code><span class="hljs-built_in">puts</span> <span class="hljs-string">"This is treasure1: #{t1.inspect}"</span>
<span class="hljs-built_in">puts</span> <span class="hljs-string">"This is treasure2: #{t2.inspect}"</span></code></pre><div class="code-file clearfix"><span>accessors3.rb</span></div>

<p>属性访问器可以同时初始化超过一个属性，你可以传递一个用逗号分隔的符号列表：</p>
<pre><code><span class="hljs-keyword">attr_reader</span> <span class="hljs-symbol">:name</span>, <span class="hljs-symbol">:description</span>
<span class="hljs-keyword">attr_writer</span>(<span class="hljs-symbol">:name</span>, <span class="hljs-symbol">:description</span>)
<span class="hljs-keyword">attr_accessor</span>(<span class="hljs-symbol">:value</span>, <span class="hljs-symbol">:id</span>, <span class="hljs-symbol">:owner</span>)</code></pre><p>和往常一样，在 Ruby 中参数列表的括号是可选的，但在我看来（为了清楚起见）括号是必选的。</p>
<div class="code-file clearfix"><span>2adventure.rb</span></div>

<p>现在让我们看看如何将属性读写用在我们的冒险游戏中。加载 <strong>2adventure.rb</strong> 程序。你会看到我在 Thing 类中创建了两个可读的属性：<code>name</code> 和 <code>description</code>。我同时也让 <code>description</code> 是可写的。然而，我不打算改变任何一个 Thing 对象的 name，所以 <code>name</code> 是不可写的：</p>
<pre><code><span class="hljs-function"><span class="hljs-title">attr_reader</span><span class="hljs-params">(:name, :description)</span></span>
<span class="hljs-function"><span class="hljs-title">attr_writer</span><span class="hljs-params">(:description)</span></span></code></pre><p>我创建了一个用来返回描述 Treasure 对象的字符串的 <code>to_s</code> 方法。回想一下，所有的 Ruby 类都有一个标准的 <code>to_s</code> 方法，<code>Thing.to_s</code> 方法覆盖（替换）了默认的方法。当您希望实现适合特定类类型的新行为时，你可以覆盖现有的方法。</p>
<h3 id="-">调用超类方法</h3>
<p>我已经决定我的游戏会有两个派生自 Thing 的后代类。Treasure 类添加了一个可读写的 <code>value</code> 属性；注意它的 <code>initialize</code> 方法中调用了超类方法，是为了在初始化新变量 <code>@value</code> 之前初始化 <code>name</code> 和 <code>description</code> 属性：</p>
<pre><code><span class="hljs-function"><span class="hljs-title">super</span><span class="hljs-params">(aName, aDescription)</span></span>
@value = aValue</code></pre><p>在这里如果我省略了对超类方法的调用，那么 <code>name</code> 和 <code>description</code> 属性将永远不会被初始化。这是因为 <code>Treasure.initialize</code> 覆盖了 <code>Thing.initialize</code>，所以当一个 Treasure 对象被创建时，<code>Thing.initialize</code> 代码不会自动被执行。</p>
<p>另一方面，<code>Room</code> 类也派生自 Thing，目前没有 <code>initialize</code> 方法；所以当一个新的 Room 对象被创建时，Ruby 会从类层次中逐层向上寻找该方法。第一个 <code>initialize</code> 方法在 Thing 中被发现；所以一个 Room 对象的 <code>name</code> 和 <code>description</code> 属性在这里被初始化。</p>
<h3 id="-">类变量</h3>
<p>在这个程序中还有一些其他的有趣的东西。在顶级类 Thing 中你会看到：</p>
<pre><code>@@num_things = <span class="hljs-number">0</span></code></pre><p>此变量的名字是以两个 <strong>@</strong> 字符开头的，<code>@@num_things</code> 定义这是一个“类变量”（class variable）。到目前为止，我们在类中使用的变量都是实例变量，以单个 <strong>@</strong> 开头，例如 <code>@name</code>。而每个类的新对象（实例）会将自己的值分配给自己的实例变量，然而所有派生自特定类的对象共享相同的类变量。我已经给 <code>@@num_things</code> 赋值为 0 以确保其是有意义的。</p>
<p>在这里，<code>@@num_things</code> 类变量用来记录在游戏运行的 Thing 对象的数量。它只是在每个新对象被创建时在它的初始化方法中简单的递增类变量（通过 <code>+=1</code>）：</p>
<pre><code>@@num_things +=<span class="hljs-number">1</span></code></pre><p>如果你接着看我的代码，你会看到我创建一了包含 rooms 的数组（array）的 Map 类。这也包括一个标准的 <code>to_s</code> 方法，可以打印出数组中每个 room 的信息。不要担心 Map 类的实现，我们将在后面章节讨论数组及其方法。</p>
<p>滚动到代码文件的底部，然后运行程序看看我是如何创建和初始化所有的对象以及使用类变量，<code>@@num_things</code>，来记录已创建的 Thing 对象的数量。</p>
<div class="note">
    <dl>
        <dt class="text-center"><strong>类变量与实例变量</strong></dt>
        <dd>
            <div class="text-center">
                <img src="./images/chapter2_Class_Variables.png" />
            </div>
            对于 Thing 类，如有三个不同的 Thing 对象（实例），<code>@name</code> 作为实例变量，因各个对象（实例）的不同而取值不同；并且各个对象之间修改 <code>@name</code> 的值并不会相互影响，是独立的。Thing 类中有一个 <code>@@num_things</code> 类变量，它被三个对象共享，其中一个修改 <code>@@num_things</code> 的值时，其它对象访问到 <code>@@num_things</code> 的值也会发生改变。所以说，实例变量 <code>@name</code> 是对象独有的，而类变量 <code>@@num_things</code> 是被所有对象共享的。
        </dd>
    </dl>
</div>

<h2 id="-">深入探索</h2>
<h3 id="-">超类</h3>
<div class="code-file clearfix"><span>super.rb</span></div>

<p>要理解 super 关键字的工作原理，请看我们的示例程序，<strong>super.rb</strong>。它包含五个相关类：Thing 类其他所有类的祖先；从 Thing 类衍生到 Thing2 类，再从 Thing2 类衍生到 Thing3 类，然后依次是 Thing4 和 Thing5 类。</p>
<p>让我们仔细看看这个层次中的前三个类：Thing 类有两个实例变量，<code>@name</code> 和 <code>@description</code> ；Thing2 类也定义了 <code>@fulldescription</code>（一个包含 <code>@name</code> 与 <code>@description</code> 的字符串）；Thing3 类则添加了另一个变量 <code>@value</code>。</p>
<p>这三个类各自包含一个 <code>initialize</code> 方法，用来在创建新对象时设置变量的值；它们各自也有一个名为 <code>aMethod</code> 的方法，用来改变一个或多个变量的值。后代类 Thing2 和 Thing3 在它们的方法中都使用了 <code>super</code> 关键字。</p>
<div class="note">
    在命令窗口运行 <b>super.rb</b> 。去按位测试代码，键入 1 到 5的一个数字，当有提示时或者键入 'q' 退出。
</div>

<p>在这段代码的底部，我写了一个 &quot;main&quot; 循环，这将在你运行该程序时执行。不要担心不懂这些语法，我们将在以后的章节中学习循环。我已经将 <code>test1</code> 到 <code>test5</code> 添加进了循环，所以你可以很容易地运行包含在不同方法中的代码。首次运行此程序时，在提示符处键入数字 <code>1</code> 然后按 Enter 键。 这将运行包含这两行代码的test1方法：</p>
<pre><code>t = <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">Thing</span>.</span></span><span class="hljs-keyword">new</span>( <span class="hljs-string">"A Thing"</span>, <span class="hljs-string">"a lovely thing full of thinginess"</span> )
t.a<span class="hljs-constructor">Method( <span class="hljs-string">"A New Thing"</span> )</span></code></pre><p>第一行在这里创建并初始化 Thing 对象，第二行调用它的 <code>aMethod</code> 方法。因为 Thing 类并非派生自其它类（事实上，所有的 Ruby 类都派生自 Object 类，它是其它所有类的祖先类），所以在这里并没有其它的事情发生。当调用 <code>Thing.initialize</code> 和 <code>Thing.aMethod</code> 方法时，输出调用了 <code>inspect</code> 方法来显示对象的内部结构。<code>inspect</code> 方法可以被所有对象调用，是个调试的工具方法。在这里，它给我们显示一个十六进制的数字，来表示这个拥有 <code>@name</code> 和 <code>@description</code> 字符串变量的特定对象。</p>
<p>现在，在提示符下，输入 <code>2</code> 来运行 <code>test2</code> 包含的代码，创建一个 Thing2 对象 <code>t2</code> 并调用 <code>t2.aMethod</code> 方法：</p>
<pre><code>t2 = <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">Thing2</span>.</span></span><span class="hljs-keyword">new</span>( <span class="hljs-string">"A Thing2"</span>, <span class="hljs-string">"a Thing2 thing of great beauty"</span> )
t2.a<span class="hljs-constructor">Method( <span class="hljs-string">"A New Thing2"</span>, <span class="hljs-string">"a new Thing2 description"</span> )</span></code></pre><p>仔细看看输出，你会看到即使 <code>t2</code> 是一个 Thing2 对象，首先被调用的却是 Thing 类的 <code>initialize</code> 方法。想理解为什么会这样，看看 Thing2 类的 <code>initialize</code> 方法的代码：</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">initialize</span><span class="hljs-params">(aName, aDescription)</span></span>
  <span class="hljs-keyword">super</span>
  @fulldescription = <span class="hljs-string">"This is <span class="hljs-subst">#{@name}</span>, which is <span class="hljs-subst">#{@description}</span>"</span>
  puts(<span class="hljs-string">"Thing2.initialize: <span class="hljs-subst">#{<span class="hljs-keyword">self</span>.inspect}</span>\n\n"</span>)
<span class="hljs-keyword">end</span></code></pre><p>这里使用了 <code>super</code> 关键字调用了 Thing2 类的祖先类或者说&quot;超类&quot; 的 <code>initialize</code> 方法。你可以从声明中看到 Thing2 类的超类是 Thing 类：</p>
<pre><code><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Thing2</span> &lt; Thing</span></code></pre><p>在 Ruby 中， 当 <code>super</code> 关键字本身单独使用时（即不带任何参数），它将会把当前方法（这里是 <code>Thing2.initialize</code>）所有参数传递给在其超类中的同名方法（这里是 <code>Thing.initialize</code>）。或者，你可以显式的指定 <code>super</code> 后的参数列表。因此，在本例中以下代码将具有相同的效果：</p>
<pre><code><span class="hljs-function"><span class="hljs-title">super</span><span class="hljs-params">(aName, aDescription)</span></span></code></pre><p>虽然允许单独使用 <code>super</code> 关键字，但是通常为了清楚起见，应该明确指定要传递给超类方法的参数列表。无论如何，如果你想要传递有限的参数，则需要一个明确的参数列表。例如，Thing2 的 <code>aMethod</code> 方法仅将 <code>Name</code> 参数传递给其超类 Thing1 的 <code>initialize</code> 方法：</p>
<p>这解释了为什么 <code>@description</code> 变量在 <code>Thing2.aMethod</code> 方法被调用时为何不会改变。</p>
<p>现在，你看看 Thing3 类，你会看到这增加了一个更多的变量，<code>@value</code>。<code>initialize</code> 方法传递了两个参数 <code>aName</code> 和 <code>aDescription</code> 给它的超类 Thing2。反过来，正如我们所看到的，Thing2 类的 <code>initialize</code> 同样地将这些参数传递给了它的超类 Thing 的 <code>initialize</code> 方法。</p>
<p>程序运行时，在提示符下输入 <code>3</code> 以查看输出。这是此次执行的代码：</p>
<pre><code>t3 = <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">Thing3</span>.</span></span><span class="hljs-keyword">new</span>(<span class="hljs-string">"A Thing 3"</span>, <span class="hljs-string">"a Thing3 full of Thing and Thing2iness"</span>,<span class="hljs-number">500</span>)
t3.a<span class="hljs-constructor">Method( <span class="hljs-string">"A New Thing3"</span>, <span class="hljs-string">"and a new Thing3 description"</span>,1000)</span></code></pre><p>注意执行流程是如何在类层次结构中向上传递的，在 Thing 中的 <code>initialize</code> 和 <code>aMethod</code> 方法在执行前匹配 Thing2 和 Thing3 中的同名方法。</p>
<p>到目前为止，我做的例程中它们并没有强制重写超类的方法。这只有当你想添加一些新行为的时候才会需要。Thing4 省略了 <code>initialize</code> 方法，但实现了 <code>aMethod</code> 方法。</p>
<p>输入 <code>4</code> 执行下面的代码：</p>
<pre><code>t4 = <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">Thing4</span>.</span></span><span class="hljs-keyword">new</span>( <span class="hljs-string">"A Thing4"</span>, <span class="hljs-string">"the nicest Thing4 you will ever see"</span>, <span class="hljs-number">10</span> )
t4.aMethod</code></pre><p>当你运行它，发现第一个可用的 <code>initialize</code> 被调用时， Thing4 对象被创建。这恰好是 <code>Thing3.initialize</code>，同时也会再次调用其祖先类 Thing2 和 Thing 的 <code>initialize</code> 方法。然而，Thing4 实现的 <code>aMethod</code> 方法没有调用它的超类方法，因此其它祖先类中的方法会被忽略，这里将立即执行。</p>
<p>最后，Thing5 继承自 Thing4，不会引入任何新的数据或方法。在提示符处输入 <code>5</code> 以执行以下代码：</p>
<pre><code>t5 = <span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">Thing5</span>.</span></span><span class="hljs-keyword">new</span>(<span class="hljs-string">"A Thing5"</span>, <span class="hljs-string">"a very simple Thing5"</span>, <span class="hljs-number">40</span>)
t5.aMethod</code></pre><p>这次你会看到，调用 <code>new</code> 方法会导致 Ruby 回溯类层次结构，直到找到第一个 <code>initialize</code> 方法。而这存在于 Thing3 中（同样也是 Thing2 和 Thing3 的初始化方法）。然而，<code>aMethod</code> 方法的第一个实现在 Thing4 中，它没有调用 <code>super</code>，所以从这里就结束了。</p>
<div class="code-file clearfix"><span>superclasses.rb</span></div>

<div class="note">
    <p>
        所有的 Ruby 类都是继承自 Object 类。
    </p>
    <p>
        Object 类本身没有超类，任何尝试获取它的超类行为都会返回 nil 。
    </p>

<pre><code><span class="hljs-keywords">begin</span>
  <span class="hljs-symbol">x</span> = <span class="hljs-symbol">x</span>.superclass
  puts(<span class="hljs-symbol">x</span>)
<span class="hljs-keywords">end</span> <span class="hljs-keywords">until</span> <span class="hljs-symbol">x</span> == nil</code></pre></div>

<h3 id="-">类中的常量</h3>
<p>有时可能需要访问在类中声明的常量（以大写开头）。让我们假设你有下面这个类：</p>
<div class="code-file clearfix"><span>classconsts.rb</span></div>

<pre><code><span class="hljs-keyword">class</span> <span class="hljs-symbol">X</span>
  <span class="hljs-symbol">A</span> = <span class="hljs-symbol">10</span>

  <span class="hljs-symbol">class</span> <span class="hljs-symbol">Y</span>
  <span class="hljs-symbol">end</span>
<span class="hljs-symbol">end</span></code></pre><p>为了访问常量 <code>A</code>，你需要使用特殊范围运算符 <code>::</code>，像这样：</p>
<pre><code>X::A</code></pre><p>类名是常量，所以这个操作符允许你访问其他类中的类，这使得可以从“嵌套”类创建对象，如类 <code>X</code> 中的类 <code>Y</code>：</p>
<pre><code>ob = X::<span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">Y</span>.</span></span><span class="hljs-keyword">new</span></code></pre><h3 id="-">局部类</h3>
<p>在 Ruby 中，定义一个类在同一个地方是不必要的，如果你愿意，你可以在程序的不同地方定义同一个类。当一个类派生自特定的超类，每个后续的类定义部分使用 <code>&lt;</code> 操作符指定超类是可选的。</p>
<p>这里我创建了两个类，A 以及 派生自 A 的 B：</p>
<div class="code-file clearfix"><span>partial_classes</span></div>

<pre><code><span class="hljs-keyword">class</span> <span class="hljs-symbol">A</span>
  <span class="hljs-symbol">def</span> <span class="hljs-symbol">a</span>
    <span class="hljs-symbol">puts</span>( "<span class="hljs-symbol">a</span>" )
  <span class="hljs-symbol">end</span>
<span class="hljs-symbol">end</span>

<span class="hljs-symbol">class</span> <span class="hljs-symbol">B</span> &lt; <span class="hljs-symbol">A</span>
  <span class="hljs-symbol">def</span> <span class="hljs-symbol">ba1</span>
    <span class="hljs-symbol">puts</span>( "<span class="hljs-symbol">ba1</span>" )
  <span class="hljs-symbol">end</span>
<span class="hljs-symbol">end</span>

<span class="hljs-symbol">class</span> <span class="hljs-symbol">A</span>
  <span class="hljs-symbol">def</span> <span class="hljs-symbol">b</span>
    <span class="hljs-symbol">puts</span>( "<span class="hljs-symbol">b</span>" )
  <span class="hljs-symbol">end</span>
<span class="hljs-symbol">end</span>

<span class="hljs-symbol">class</span> <span class="hljs-symbol">B</span> &lt; <span class="hljs-symbol">A</span>
  <span class="hljs-symbol">def</span> <span class="hljs-symbol">ba2</span>
    <span class="hljs-symbol">puts</span>( "<span class="hljs-symbol">ba2</span>" )
  <span class="hljs-symbol">end</span>
<span class="hljs-symbol">end</span></code></pre><p>现在，如果我创建一个 B 对象，A 和 B 的所有方法都是可用的：</p>
<pre><code>ob = B<span class="hljs-selector-class">.new</span>
ob<span class="hljs-selector-class">.a</span>
ob<span class="hljs-selector-class">.b</span>
ob<span class="hljs-selector-class">.ba1</span>
ob.ba2</code></pre><p>你还可以将功能添加到 Ruby 的标准类中，例如 Array：</p>
<pre><code><span class="hljs-keyword">class</span> <span class="hljs-symbol">Array</span>
  <span class="hljs-symbol">def</span> <span class="hljs-symbol">gribbit</span>
    <span class="hljs-symbol">puts</span>( "<span class="hljs-symbol">gribbit</span>" )
  <span class="hljs-symbol">end</span>
<span class="hljs-symbol">end</span></code></pre><p>这会将 <code>gribbit</code> 方法添加到 Array 类中，以便现在可以执行以下代码：</p>
<pre><code>[<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>].gribbit</code></pre>